package org.kgduyjj.toolkit.idcard;

import java.util.Optional;
import java.util.regex.Pattern;

import org.kgduyjj.toolkit.enumtype.IDCardFormat;
import org.kgduyjj.toolkit.util.DateUtils;
import org.kgduyjj.toolkit.util.ObjectUtils;
import org.kgduyjj.toolkit.util.StringUtils;

import cn.hutool.core.exceptions.UtilException;

/**
 * 身份证号码辅助工具类
 * 
 * @author Kgduyjj
 * @since 2020-07-30
 * @version 1.0.0-Alpha
 *
 */
public final class IdCardUtils {
    /** 中国公民身份证日期格式 */
    public static final String CN_DATE_PATTERN = "yyyyMMdd";
    /** 本体码乘积因子工厂 */
    private static final int[] POWER = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
    /** 校验码工厂 */
    private static final String[] CKECK_DIGIT = { "1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2" };
    /** 空字符串 */
    private static final String EMPTY = "";
    /** 简单的18位身份号码验证 正则表达式 */
    private static final String IDCARD_SIMPLE_PATTERN = "^[1-9]{1}[0-9]{5}[1|2]{1}[0-9]{3}(0?[1-9]|1[0-2])((0?[1-9])|((1|2)[0-9])|30|31)[0-9]{3}[0-9|x|X]{1}$";

    private IdCardUtils() {
    }

    /**
     * 生成一个中国公民身份证号码
     * 
     * @param districtCode 地区编码
     * @param birthday     出生年月日 [yyyyMMdd]
     * @param seq          顺序码 [xxx]
     * @return
     */
    public static String genCNCard(String districtCode, String birthday, String seq) {
        String iCard18 = "";
        String iCard17 = StringUtils.format("{}{}{}", districtCode, birthday, seq);
        char[] cArr = iCard17.toCharArray();
        if (cArr != null) {
            int[] iCard = converCharToInt(cArr);
            int iSum17 = getPowerSum(iCard);
            String sVal = getCheckCode18(iSum17);
            if (sVal.length() > 0) {
                iCard18 = iCard17 + sVal;
            } else {
                return null;
            }
        }

        return iCard18;
    }

    /**
     * 简单的格式校验，不对其校验码做计算校验
     * 
     * @param idCardNo
     * @return
     */
    public static boolean validateSimple(final String idCardNo) {
        if (StringUtils.isBlank(idCardNo)) {
            return false;
        }
        return (Pattern.matches(IDCARD_SIMPLE_PATTERN, idCardNo));
    }

    /**
     * 校验中国公民18位身份证号码
     * 
     * @param idCardNo
     * @return
     */
    public static boolean validate(String idCardNo) {
        // check idCardNo length is right
        if (!validateSimple(idCardNo)) {
            return false;
        }
        if (!validateCheckDigit(idCardNo)) {
            return false;
        }
        return true;
    }

    /**
     * 通过身份证格式化枚举获取所需要的字段
     * 
     * @param idCardNo
     * @param format
     * @return
     */
    public static String truncation(String idCardNo, IDCardFormat formatter) {
        if (!validateCNCardLenght(idCardNo)) {
            throw new UtilException("idCardNo length is wrong");
        }
        if (ObjectUtils.isNull(formatter)) {
            return null;
        }
        return formatter.truncation(idCardNo);
    }

    /**
     * 获取身份证号码最后一位
     * 
     * @param idCardNo
     * @return
     */
    public static String getCheckCode(String idCardNo) {
        return truncation(idCardNo, IDCardFormat.SELF_CHECKCODE);
    }

    /**
     * 获取身份证号码倒数第二位
     * 
     * @param idCardNo
     * @return
     * @throws UtilException
     */
    public static String getGerder(String idCardNo) throws UtilException {
        return truncation(idCardNo, IDCardFormat.ALIAS_SEX);
    }

    /**
     * 校验身份证号码是否为男性
     * 
     * @param idCardNo
     * @return
     * @throws UtilException
     */
    public static boolean validateMale(String idCardNo) throws UtilException {
        String gender = getGerder(idCardNo);
        if (!isDigital(gender)) {
            throw new UtilException("Validate idCardNo Gender Error,The 17th position isn't Digital");
        }
        return (Integer.valueOf(gender) % 2 != 0);
    }

    /**
     * 身份证号码最后一位是否为罗马数字10
     * 
     * @param idCardNo
     * @return
     * @throws UtilException
     */
    public static boolean isLastDigitTen(String idCardNo) throws UtilException {
        if (!validateCNCardLenght(idCardNo)) {
            throw new UtilException("idCardNo length is wrong");
        }
        String checkCode = getCheckCode(idCardNo);
        return ("x".equals(checkCode) || "X".equals(checkCode));
    }

    /**
     * 从身份证号码中提取年龄
     * 
     * @param idCardNo
     * @return
     */
    public static Integer fetchAge(String idCardNo) {
        if (!validate(idCardNo)) {
            return null;
        }
        return Optional.ofNullable(truncation(idCardNo, IDCardFormat.BIRTHDAY))
                .map(birthday -> DateUtils.parse(birthday, CN_DATE_PATTERN))
                .map(bdate -> DateUtils.comparerYears(bdate, DateUtils.now())).orElse(null);
    }

    /**
     * 将字符数组转换成数字数组
     * 
     * @param chars
     * @return
     */
    private static int[] converCharToInt(char[] chars) {
        int len = chars.length;
        int[] iArr = new int[len];
        try {
            for (int i = 0; i < len; i++) {
                iArr[i] = Integer.parseInt(String.valueOf(chars[i]));
            }
        } catch (NumberFormatException e) {
            throw new UtilException("Conver Char to Integer error", e);
        }
        return iArr;
    }

    /**
     * 将每张身份证与相应的加权因子相乘位置，计算本体码的总值
     * 
     * @param iArr
     * @return
     */
    private static int getPowerSum(int[] iArr) {
        int iSum = 0;
        if (iArr.length != 17) {
            throw new UtilException("ID Card corresponding postition length is wrong");
        }
        if (POWER.length == iArr.length) {
            for (int i = 0; i < iArr.length; i++) {
                for (int j = 0; j < POWER.length; j++) {
                    if (i == j) {
                        iSum = iSum + iArr[i] * POWER[j];
                    }
                }
            }
        }
        return iSum;
    }

    /**
     * 在计算和赋值之后，再计算11模的值求余数，最后确定校验码。
     * 
     * @param iSum
     * @return
     */
    private static String getCheckCode18(int iSum) {
        int index = iSum % 11;
        try {
            if (CKECK_DIGIT[index] == null) {
                throw new UtilException("Get Check Code Error");
            }
            return CKECK_DIGIT[index];
        } catch (UtilException e) {
            throw e;
        } catch (Exception e) {
            throw new UtilException("Get CN Card validate code error", e);
        }
    }

    /**
     * 校验身份证长度是否为18位
     * 
     * @param idCardNo
     * @return
     */
    private static boolean validateCNCardLenght(String idCardNo) {
        return (idCardNo != null && idCardNo.length() == 18);
    }

    /**
     * 获得身份证号码前17位本体码
     * 
     * @param idCardNo
     * @return
     */
    private static String getTop17(String idCardNo) {
        if (!validateCNCardLenght(idCardNo)) {
            throw new UtilException("idCardNo length is wrong");
        }
        return idCardNo.substring(0, 17);
    }

    /**
     * 校验是否为纯数字字符串
     * 
     * @param str
     * @return
     */
    private static boolean isDigital(String str) {
        return (str == null || EMPTY.equals(str) ? false : str.matches("^[0-9]*$"));
    }

    /**
     * 验证校验码是否正确
     * 
     * @param idCardNo
     * @return
     */
    private static boolean validateCheckDigit(String idCardNo) {
        int[] iFullBnumber = converCharToInt(getTop17(idCardNo).toCharArray());
        int iSum17 = getPowerSum(iFullBnumber);
        // 获取校验位
        String iCheckDigit = getCheckCode18(iSum17);
        String checkDigit = getCheckCode(idCardNo);
        if (StringUtils.anyBlank(iCheckDigit, checkDigit)) {
            return false;
        }
        return checkDigit.toUpperCase().equals(iCheckDigit.toUpperCase());
    }
}